function BuildOrderEditor(container) {
  container.addClass('container');

  var buildables = $('<ul>').addClass('buildables');
  container.append(buildables);

  var build_order = $('<ul>').addClass('build-order');
  var game_start = this.createBuildOrderEntry('Game start');
  game_start.attr('draggable', false);
  build_order.append(game_start);
  container.append(build_order);

  container.on('dragover', function(e) {
    if (e.originalEvent.dataTransfer.effectAllowed == 'move') {
      e.originalEvent.dataTransfer.dropEffect = 'move';
      e.preventDefault();
    }
  });

  this.onOrderUpdate();

  $.ajax({
    url: '/api/getBuildables',
    data: {
      'version': '1.4.3 BU',
      'race': 'Protoss',
    },
    dataType: 'json',
    context: this,
    success: this.onBuildablesResponse,
  });
}

BuildOrderEditor.prototype.EventType = {
  GAME_START: 1,
  BUILD_START: 2,
  BUILD_COMPLETE: 3,
  ASSIGN_WORKER: 4,
  RESOURCE_RETURN: 5,
  ERROR: 6,
};

BuildOrderEditor.prototype.getUnitImage = function(unit_name) {
  var image_name = unit_name.replace(' ', '_');
  return $('<img src="images/' + image_name + '.png">');
};

BuildOrderEditor.prototype.onBuildablesResponse = function(data) {
  var buildables = $('.buildables');
  for (var i = 0; i < data.length; ++i) {
    buildables.append(this.createBuildableEntry(data[i].name));
  }
};

BuildOrderEditor.prototype.createBuildableEntry = function(unit_name) {
  var boe = this;

  var entry = $('<li>');
  entry.append(this.getUnitImage(unit_name));
  entry.on('dragstart', function(e) {
    e.originalEvent.dataTransfer.setData('text/plain', unit_name);
    e.originalEvent.dataTransfer.effectAllowed = 'copy';
  });
  entry.on('dragend', function(e) {
    if (e.originalEvent.dataTransfer.dropEffect == 'copy') {
      boe.onOrderUpdate();
    }
  });
  return entry;
};

BuildOrderEditor.prototype.createBuildOrderEntry = function(unit_name) {
  var boe = this;

  var entry = $('<li>');
  entry.attr('draggable', true);

  var description = $('<div>').addClass('description');
  description.append(this.getUnitImage(unit_name));
  description.append($('<span>' + unit_name + '</span>').addClass('unit_name'));
  entry.append(description);

  var supply = $('<div>').addClass('supply');
  supply.append($('<span>...</span>').addClass('current_supply'));
  supply.append('/');
  supply.append($('<span>...</span>').addClass('max_supply'));
  entry.append(supply);

  var timing = $('<div>').addClass('timing');
  timing.append($('<span>...</span>').addClass('start_time'));
  timing.append('⇢');
  timing.append($('<span>...</span>').addClass('end_time'));
  timing.append(' (');
  timing.append($('<span>...</span>').addClass('build_time'));
  timing.append(')');
  entry.append(timing);

  var cost = $('<div>').addClass('cost');
  cost.append(this.getUnitImage('Minerals'));
  cost.append($('<span>...</span>').addClass('cost_minerals'));
  cost.append(' ');
  cost.append(this.getUnitImage('Gas'));
  cost.append($('<span>...</span>').addClass('cost_gas'));
  cost.append(' ');
  cost.append(this.getUnitImage('Supply'));
  cost.append($('<span>...</span>').addClass('cost_supply'));
  entry.append(cost);

  var error = $('<div>').addClass('error');
  entry.append(error);

  var potential = null;
  var allow_drop = true;

  // Fired on targets
  entry.on('dragover', function(e) {
    if (!allow_drop) { return; }
    e.originalEvent.dataTransfer.dropEffect = e.originalEvent.dataTransfer.effectAllowed;
    e.preventDefault();
    if (potential) { return; }
    potential = $('<li>').addClass('potential');
    entry.after(potential);
  });
  entry.on('dragleave', function(e) {
    if (potential) {
      potential.remove();
      potential = null;
    }
  });
  entry.on('drop', function(e) {
    if (potential) {
      potential.remove();
      potential = null;
    }
    var new_unit_name = e.originalEvent.dataTransfer.getData('text/plain');
    var new_entry = boe.createBuildOrderEntry(new_unit_name);
    $(this).after(new_entry);
    boe.invalidateBuildOrder(new_entry);
    e.preventDefault();
  });

  // Fired on sources
  entry.on('dragstart', function(e) {
    allow_drop = false;
    $(this).addClass('move-source');
    var img = boe.getUnitImage(unit_name);
    e.originalEvent.dataTransfer.setDragImage(img.get(0), 0, 0);
    e.originalEvent.dataTransfer.setData('text/plain', unit_name);
    e.originalEvent.dataTransfer.effectAllowed = 'move';
  });
  entry.on('dragend', function(e) {
    if (e.originalEvent.dataTransfer.dropEffect == 'move') {
      $(this).remove();
      boe.onOrderUpdate();
    } else {
      allow_drop = true;
      $(this).removeClass('move-source');
    }
  });
  return entry;
};

BuildOrderEditor.prototype.onOrderUpdate = function() {
  var params = [];
  $('.build-order li .unit_name').each(function(i, element) {
    if (element.innerHTML == 'Game start') { return; }
    params.push({
      name: 'build',
      value: element.innerHTML,
    });
  });
  params.push({
    name: 'version',
    value: '1.4.3 BU',
  });
  params.push({
    name: 'race',
    value: 'Protoss',
  });
  params.push({
    name: 'map',
    value: 'Test Map',
  });
  $.ajax({
    url: '/api/simulateBuild',
    data: params,
    traditional: true,
    dataType: 'json',
    context: this,
    success: this.onOrderResponse,
  });
};

BuildOrderEditor.prototype.onOrderResponse = function(data) {
  var boe = this;

  var entries = $('.build-order li').get();
  var entry_by_id = {};
  var game_started = false;

  for (var i = 0; i < data.events.length; ++i) {
    var event = data.events[i];
    if (event.type == this.EventType.GAME_START) {
      game_started = true;
      var entry = $(entries.shift());
      entry.find('.current_supply')[0].innerHTML = event.current_supply;
      entry.find('.max_supply')[0].innerHTML = event.max_supply;
      entry.find('.start_time')[0].innerHTML = event.time;
      entry.find('.end_time')[0].innerHTML = event.time;
      entry.find('.build_time')[0].innerHTML = 0;
      entry.find('.cost_minerals')[0].innerHTML = 0;
      entry.find('.cost_gas')[0].innerHTML = 0;
      entry.find('.cost_supply')[0].innerHTML = 0;
    }
    if (event['object_id']) {
      var entry = entry_by_id[event['object_id']];
      if (!entry) {
        if (game_started) {
          entry = entry_by_id[event['object_id']] = $(entries.shift());
          var unit_name = entry.find('.unit_name')[0].innerHTML;
          if (unit_name != event.buildable) {
            console.log('Unit name mismatch: "' + unit_name + '" vs. "' + event.buildable + '"');
          }
        } else {
          // create hidden object to store state
          entry = entry_by_id[event['object_id']] = this.createBuildOrderEntry(event.buildable);
        }
      }
      var buildable = data['buildables'][event.buildable];
      entry.find('.cost_minerals')[0].innerHTML = buildable.cost_minerals;
      entry.find('.cost_gas')[0].innerHTML = buildable.cost_gas;
      entry.find('.cost_supply')[0].innerHTML = buildable.cost_supply;
      switch (event.type) {
        case this.EventType.BUILD_START:
          entry.find('.current_supply')[0].innerHTML = event.current_supply;
          entry.find('.max_supply')[0].innerHTML = event.max_supply;
          entry.find('.start_time')[0].innerHTML = event.time;
          break;
        case this.EventType.BUILD_COMPLETE:
          entry.find('.end_time')[0].innerHTML = event.time;
          entry.find('.build_time')[0].innerHTML = event.time - entry.find('.start_time')[0].innerHTML;
          break;
        case this.EventType.ERROR:
          entry.find('.error')[0].innerHTML = event.value.toUpperCase();
          break;
      }
    }
  }
};

BuildOrderEditor.prototype.invalidateBuildOrder = function(first_entry) {
  this.invalidateBuildEntry(first_entry);
  var following_entries = first_entry.nextAll();
  for (var i = 0; i < following_entries.length; ++i) {
    this.invalidateBuildEntry($(following_entries[i]));
  }
};

BuildOrderEditor.prototype.invalidateBuildEntry = function(entry) {
  entry.find('.current_supply')[0].innerHTML = '...';
  entry.find('.max_supply')[0].innerHTML = '...';
  entry.find('.start_time')[0].innerHTML = '...';
  entry.find('.end_time')[0].innerHTML = '...';
  entry.find('.build_time')[0].innerHTML = '...';
  entry.find('.error')[0].innerHTML = '';
};

$(document).ready(function() {
  var x = new BuildOrderEditor($('#container'));
});
